1 /*
2  * Hunt - a framework for web and console application based on Collie using Dlang development
3  *
4  * Copyright (C) 2015-2017  Shanghai Putao Technology Co., Ltd
5  *
6  * Developer: HuntLabs
7  *
8  * Licensed under the Apache-2.0 License.
9  *
10  */
11 
12 module hunt.http.request;
13 
14 import std.exception;
15 
16 import kiss.container.ByteBuffer;
17 import collie.codec.http;
18 import collie.codec.http.server.requesthandler;
19 import collie.codec.http.server.httpform;
20 import collie.utils.memory;
21 
22 import hunt.http.response;
23 import hunt.http.session;
24 import hunt.http.cookie;
25 import hunt.http.exception;
26 import hunt.http.nullbuffer;
27 import hunt.routing.route;
28 import hunt.utils.time;
29 import hunt.application.application;
30 import hunt.security.acl.User;
31 
32 import std.string;
33 import std.conv;
34 import std.json;
35 
36 alias CreatorBuffer = Buffer delegate(HTTPMessage) nothrow;
37 alias DoHandler = void delegate(Request) nothrow;
38 
39 final class Request : RequestHandler
40 {
41 	this(CreatorBuffer cuffer, DoHandler handler, uint maxsize = 8 * 1024 * 1024)
42 	{
43 		_creatorBuffer = cuffer;
44 		_handler = handler;
45 		_maxBodySize = maxsize;
46 	}
47 
48 	@property HTTPForm postForm()
49 	{
50 		if (_body && ( _form is null))
51 			_form = new HTTPForm(header(HTTPHeaderCode.CONTENT_TYPE),_body);
52 		return _form;
53 	}
54 
55 	@property HTTPMessage Header(){return _headers;}
56 
57 	@property Buffer Body(){
58 		if(_body)
59 			return _body; 
60 		else 
61 			return defaultBuffer;
62 	}
63 	
64     @property ubyte[] ubyteBody(){
65 		if(!_uBody.length){
66             Body.rest(0);
67             Body.readAll((in ubyte[] data){
68                 _uBody ~= data;
69             });
70         }
71 		return _uBody; 
72 	}
73 
74 	@property Route route() { return _route; }
75 
76 	@property void route(Route value) { _route = value; }
77 
78 	@property User user()
79 	{
80 		return this._user;
81 	}
82 
83 	@property void user(User user)
84 	{
85 		this._user = user;
86 	}
87 
88 	@property string[string] mate(){return _mate;}
89 
90 	@property string path(){return Header.getPath;}
91 
92 	@property string method(){return Header.methodString;}
93 
94 	@property string host(){return header(HTTPHeaderCode.HOST);}
95 
96 	string header(HTTPHeaderCode code){
97 		return _headers.getHeaders.getSingleOrEmpty(code);
98 	}
99 
100 	string header(string key){
101 		return _headers.getHeaders.getSingleOrEmpty(key);
102 	}
103 
104 	bool headerExists(HTTPHeaderCode code){
105 		return _headers.getHeaders.exists(code);
106 	}
107 
108 	bool headerExists(string key){
109 		return _headers.getHeaders.exists(key);
110 	}
111 
112 	int headersForeach(scope int delegate(string key, string value) each){
113 		return _headers.getHeaders.opApply(each);
114 	}
115 
116 	int headersForeach(scope int delegate(HTTPHeaderCode code,string key, string value) each){
117 		return _headers.getHeaders.opApply(each);
118 	}
119 
120 	bool headerValueForeach(string name,scope bool delegate(string value) func){
121 		return _headers.getHeaders.forEachValueOfHeader(name,func);
122 	}
123 
124 	bool headerValueForeach(HTTPHeaderCode code,scope bool delegate(string value) func){
125 		return _headers.getHeaders.forEachValueOfHeader(code,func);
126 	}
127 
128 	@property string clientIp()
129 	{
130 		string XFF = header("X-Forwarded-For");
131 		string[] xff_arr = split(XFF,", ");
132 		if(xff_arr.length > 0)
133 		{
134 			return xff_arr[0];
135 		}
136 		string XRealIP = header("X-Real-IP");
137 		if(XRealIP.length > 0)
138 		{
139 			return XRealIP;
140 		}
141 		return clientAddress.toAddrString();
142 	}
143 	@property string referer()
144 	{
145 		string rf = header("Referer");
146 		string[] rfarr = split(rf,", ");
147 		if(rfarr.length)
148 		{
149 			return rfarr[0];
150 		}
151 		return "";
152 	}
153 	@property Address clientAddress(){return _headers.clientAddress();}
154 
155 	string getMate(string key,string value = null)
156 	{
157 		return _mate.get(key, value);
158 	}
159 
160 	void addMate(string key, string value)
161 	{
162 		_mate[key] = value;
163 	}
164 
165 	Session getSession(string sessionName = "hunt_session")
166 	{
167 		auto sessionId = getCookieValue(sessionName);
168 		if(!sessionId.length)
169 		{
170 			auto _tmp = new Session(Application.getInstance().getSessionStorage());
171 			createResponse().setCookie(sessionName, _tmp.sessionId);
172 			return _tmp;
173 		}
174 
175 		return new Session(sessionId,Application.getInstance().getSessionStorage()); 
176 	}
177 
178 	@property Cookie[string] cookies()
179 	{
180 		if (_cookies.length == 0)
181 		{
182 			string cookie = header(HTTPHeaderCode.COOKIE);
183 			_cookies = parseCookie(cookie);
184 		}
185 
186 		return _cookies;
187 	}
188 	
189 	private Cookie getCookie(string key)
190 	{
191 		return cookies.get(key,null);
192 	}
193 
194 	string getCookieValue(string key)
195 	{
196 		auto cookie = this.getCookie(key);
197 		if(cookie is null)
198 		{
199 			return ""; 
200 		}
201 		return cookie.value;
202 	}
203 
204     @property JSONValue json()
205     {
206         if(_json == JSONValue.init){
207             _json = parseJSON(cast(string)ubyteBody()); 
208         }
209         return _json;
210     }
211     auto json(T)(string key)
212     {
213         import std.traits;
214         auto obj = (key in (json().objectNoRef));
215         if(obj is null)
216             return T.init;
217         static if(isIntegral!(T))
218             return cast(T)((*obj).integer);
219         else static if(is(T == string))
220             return (*obj).str;
221         else static if(is(FloatingPointTypeOf!T X))
222             return cast(T)((*obj).floating);
223         else static if(is(T == bool)){
224             if(obj.type == JSON_TYPE.TRUE)
225                 return true;
226             else if(obj.type == JSON_TYPE.FALSE)
227                 return false;
228             else {
229                 throw new Exception("json error");
230                 return false;
231             }
232         } else {
233             return (*obj);
234         }
235     }
236 	///get queries
237 	@property string[string] queries()
238 	{
239 		return _headers.queryParam();
240 	}
241 	/// get a query
242 	T get(T = string)(string key, T v = T.init)
243 	{
244 		import std.conv;
245 		auto tmp = queries();
246 		if(tmp is null)
247 		{
248 			return v;   
249 		}
250 		auto _v = tmp.get(key, "");
251 		if(_v.length)
252 		{
253 			return to!T(_v);
254 		}
255 		return v;
256 	}
257 
258 	/// get a post
259 	T post(T = string)(string key, T v = T.init)
260 	{
261 		import std.conv;
262 		auto form = postForm();
263 		if(form is null) return v;
264 		auto _v = postForm.getFromValue(key);
265 		if(_v.length)
266 		{
267 			return to!T(_v);
268 		}
269 		return v;
270 	}
271 	//	alias _req this;
272 
273 	/// GET FILE , if return NULL , file is null
274 	auto file(string key)
275 	{
276 		return postForm.getFileValue(key);
277 	}
278 
279 	@property ref string[string] materef() {return _mate;}
280 
281 	Response createResponse()
282 	{
283 		if(_error != HTTPErrorCode.NO_ERROR)
284 			throw new CreateResponseException("http error is : " ~ to!string(_error));
285 		if(_res is null) {
286 			_res = new Response(_downstream);
287 		}
288 		return _res;
289 	}
290 	
291 	@property void action(string value)
292 	{
293 		_action = value;
294 	}
295 	
296 	@property string action()
297 	{
298 		return _action;
299 	}
300 	
301 protected:
302 	override void onBody(const ubyte[] data) nothrow {
303 		collectException((){
304 				if(fristBody){
305 					_body = _creatorBuffer(_headers);
306 					fristBody = false;
307 					if(_body is null) {
308 						onError(HTTPErrorCode.FRAME_SIZE_ERROR);
309 						return;
310 					}
311 				}
312 				if(_body) {
313 					_body.write(data);
314 					if(_body.length > _maxBodySize){
315 						onError(HTTPErrorCode.FRAME_SIZE_ERROR);
316 						gcFree(_body);
317 						_body = null;
318 					}
319 				}
320 			}());
321 	}
322 
323 	override void onEOM() nothrow {
324 		if(_error == HTTPErrorCode.NO_ERROR)
325 			_handler(this);
326 	}
327 
328 	override void requestComplete() nothrow {
329 		collectException((){
330 				_error = HTTPErrorCode.STREAM_CLOSED;
331 				import collie.utils.memory;
332 				if(_body)gcFree(_body);
333 				if(_headers)gcFree(_headers);
334 				if(_res)gcFree(_res);
335 			}());
336 	}
337 
338 	override void onResquest(HTTPMessage headers) nothrow {
339 		_headers = headers;
340 	}
341 
342 	override void onError(HTTPErrorCode code) nothrow {
343 		collectException((){
344 				scope(exit) {
345 					_downstream = null;
346 				}
347 				_error = code;
348 				if(_error == HTTPErrorCode.REMOTE_CLOSED)
349 					return;
350 				if(_res is null){
351 					_res = new Response(_downstream);
352 				}
353 				if(_error == HTTPErrorCode.TIME_OUT){
354 					_res.setHttpStatusCode(408);
355 				} else if(_error ==  HTTPErrorCode.FRAME_SIZE_ERROR){
356 					_res.setHttpStatusCode(429);
357 				} else {
358 					_res.setHttpStatusCode(502);
359 				}
360 				_res.done();
361 				//_res.clear();
362 			}());
363 	}
364 
365 private:
366 	User _user;
367 	Route _route;
368 	string[string] _mate;
369 	Cookie[string] _cookies;
370 	Buffer _body;
371     JSONValue _json;
372     ubyte[] _uBody;
373 	HTTPMessage _headers;
374 	HTTPForm _form;
375 	Response _res;
376 	HTTPErrorCode _error = HTTPErrorCode.NO_ERROR;
377 	CreatorBuffer _creatorBuffer;
378 	uint _maxBodySize;
379 	DoHandler _handler;
380 	bool fristBody = true;
381 	string _action;
382 }